//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - SaaS Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================


//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - S+S Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using System.Text;
using System.Threading;
using System.Xml;
using System.IO;

using System.IdentityModel.Policy;
using System.IdentityModel.Tokens;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;

using LitwareHR.PublicApp.WinUX.Properties;
using Microsoft.Practices.EnterpriseLibrary.ExceptionHandling;
using System.Globalization;


namespace LitwareHR.PublicApp.WinUX.Security.BrokeredSender
{    
    /// <summary> 
    /// Helper class used as cache for security tokens 
    /// </summary>
    static public class SecurityTokenCacheStore
    {
        private static Dictionary<Uri, SecurityToken> _tokens = new Dictionary<Uri, SecurityToken>();
        private static ReaderWriterLock _tokenLock = new ReaderWriterLock();

        private const int DefaultTimeout = 1000;
        private const string StsTokenKeyPrefix = "{27C96656-DF32-4773-A09C-6984E3C83177}";

        static public SecurityToken GetSecurityToken(Uri endpoint)
        {
            if (!String.IsNullOrEmpty(Settings.Default.SecurityToken))
            {
                string securityToken = Properties.Settings.Default.SecurityToken;
                PopulateCache(securityToken);
            }

            return GetSecurityTokenFromDictionary(endpoint);
        }

        static public void AddSecurityToken(Uri endpoint, SecurityToken token)
        {
            try
            {
                string serializedToken = SerializeCachedToken(endpoint, token as GenericXmlSecurityToken);
                Properties.Settings.Default.SecurityToken = serializedToken;
                Properties.Settings.Default.Save();
            }
            catch (Exception ex)
            {
                ExceptionPolicy.HandleException(ex, "Default Policy");
            }

            AddSecurityTokenToDictionary(endpoint, token);
        }

        static private string SerializeCachedToken(Uri endpoint,  GenericXmlSecurityToken token)
        {
            XmlDocument doc = new XmlDocument();
            MemoryStream stream = new MemoryStream();
            XmlTextWriter writer = new XmlTextWriter(stream, Encoding.UTF8);
            
            writer.WriteStartElement("Entry");
            writer.WriteElementString("endpoint", endpoint.AbsoluteUri);
            writer.WriteStartElement("Token");
            
            writer.WriteStartElement("XML");
            token.TokenXml.WriteTo(writer);
            writer.WriteEndElement();
            
            SymmetricSecurityKey key = (SymmetricSecurityKey)(token.SecurityKeys[0]);
            writer.WriteElementString("Key", Convert.ToBase64String(key.GetSymmetricKey()));
            writer.WriteElementString("Id", token.Id);
            writer.WriteElementString("ValidFrom", Convert.ToString(token.ValidFrom, CultureInfo.InvariantCulture));
            writer.WriteElementString("ValidTo", Convert.ToString(token.ValidTo, CultureInfo.InvariantCulture));
            WSSecurityTokenSerializer serializer = new WSSecurityTokenSerializer();
            writer.WriteStartElement("InternalTokenReference");
            serializer.WriteKeyIdentifierClause(writer, token.InternalTokenReference);
            writer.WriteEndElement();
            writer.WriteStartElement("ExternalTokenReference");
            serializer.WriteKeyIdentifierClause(writer, token.ExternalTokenReference);
            writer.WriteEndElement();

            writer.WriteEndElement(); // token
            writer.WriteEndElement(); // entry

            writer.Flush();

            stream.Seek(0, SeekOrigin.Begin);
 
            doc.Load(stream); 
            
            return doc.InnerXml ;
        }

        static void PopulateCache(string securityToken)
        {
            if (securityToken == null)
                return;

            XmlTextReader reader = new XmlTextReader(securityToken, XmlNodeType.Document, null);
            while (reader.IsStartElement("Entry"))
            {
                reader.ReadStartElement();
                Uri endpoint = new Uri(reader.ReadElementString("endpoint"));
                
                reader.ReadStartElement("Token");
                reader.ReadStartElement("XML");
                XmlDocument doc = new XmlDocument();
                XmlElement tokenXml = doc.ReadNode(reader) as XmlElement;
                reader.ReadEndElement();
                byte[] key = Convert.FromBase64String(reader.ReadElementString("Key"));
                reader.ReadElementString("Id");
                DateTime validFrom = Convert.ToDateTime(reader.ReadElementString("ValidFrom"), CultureInfo.InvariantCulture);
                DateTime validTo = Convert.ToDateTime(reader.ReadElementString("ValidTo"), CultureInfo.InvariantCulture);
                WSSecurityTokenSerializer serializer = new WSSecurityTokenSerializer();
                reader.ReadStartElement("InternalTokenReference");
                SecurityKeyIdentifierClause internalReference = serializer.ReadKeyIdentifierClause(reader);
                reader.ReadEndElement();
                reader.ReadStartElement("ExternalTokenReference");
                SecurityKeyIdentifierClause externalReference = serializer.ReadKeyIdentifierClause(reader);
                reader.ReadEndElement();
                reader.ReadEndElement(); // token
                reader.ReadEndElement(); // entry

                List<IAuthorizationPolicy> policies = new List<IAuthorizationPolicy>();
                GenericXmlSecurityToken token = new GenericXmlSecurityToken(tokenXml, new BinarySecretSecurityToken(key), validFrom, validTo, internalReference, externalReference,
                    new ReadOnlyCollection<IAuthorizationPolicy>(policies));

                AddSecurityTokenToDictionary(endpoint, token as SecurityToken);
            }
        }

        private static void AddSecurityTokenToDictionary(Uri endpoint, SecurityToken token)
        {
            _tokenLock.AcquireWriterLock(DefaultTimeout);
            try
            {
                if (_tokens.ContainsKey(endpoint))
                    _tokens.Remove(endpoint);

                _tokens.Add(endpoint, token);
            }

            finally
            {
                _tokenLock.ReleaseWriterLock();
            }
        }
               
        private static SecurityToken GetSecurityTokenFromDictionary(Uri endpoint)
        {
            SecurityToken token = null;
            _tokenLock.AcquireReaderLock(DefaultTimeout);
            try
            {
                _tokens.TryGetValue(endpoint, out token);
                return token;
            }
            finally
            {
                _tokenLock.ReleaseReaderLock();
            }
        }

        static public void Clear()
        {
            ClearFromDictionary();
            ClearFromProfile();
        }

        private static void ClearFromProfile()
        {
            Properties.Settings.Default.SecurityToken = null;
        }

        private static void ClearFromDictionary()
        {
            _tokenLock.AcquireReaderLock(DefaultTimeout);
            try
            {
                _tokens.Clear();
            }
            finally
            {
                _tokenLock.ReleaseReaderLock();
            }
        }       
    }
}
